package ch.ethz.fcl.metrobuzz.render.techniques.circos.version1;

import java.util.List;

import javax.vecmath.Vector2d;

import ch.ethz.fcl.metrobuzz.algorithm.sort.SortData;
import ch.ethz.fcl.metrobuzz.algorithm.sort.heapsort.HeapSort;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * This table data structure is the representation of distribution of traffic on
 * junction nodes/stops 0: passengers entering/exiting the node/stop col:
 * incoming traffic on line #col row: outgoing traffic on line #row total 0 1 2
 * 3 0 120 30 70 20 1 20 40 10 10 2 20 30 70 20 3 40 20 40 100
 * 
 * @author crazyfrog
 */
public class CircosMatrix1 {
	private float[][] distri;
	private float[] incoming;
	private float[] outgoing;

	// records the direction for each eclipse
	private Vector2d[] dirs;

	// records the color index for each eclipse
	private int[] colorIndex;
	private int size;

	// render order of other eclipses for each eclipse
	private int[][] outRenderOrder;
	private int[][] inRenderOrder;
	private int[] renderOrder;

	// sum of incoming and outgoing for each eclipse
	private float[] sum;

	public CircosMatrix1(int size) {
		this.size = size;
		distri = new float[size][size];
		incoming = new float[size];
		outgoing = new float[size];
		dirs = new Vector2d[size];
		colorIndex = new int[size];

		float angle = (float) Math.PI * 2 / size;
		Vector2d xDir = new Vector2d(1, 0);
		for (int i = 0; i < size; i++) {
			dirs[i] = MBTools.rotate(xDir, angle * (i + 1));
			colorIndex[i] = i;
		}
	}

	/**
	 * traffic between roa d #row to road #col
	 * 
	 * @param row
	 * @param col
	 * @param dis
	 */
	public void setData(int row, int col, float dis) {
		if (row < 0 || row >= size)
			return;
		if (col < 0 || col >= size)
			return;

		if (row == col)
			return;

		distri[row][col] = dis;
	}

	public void setDirection(int index, Vector2d dir) {
		if (index < 0 || index >= size)
			return;

		dirs[index] = dir;
	}

	public void setColor(int index, int colorCode) {
		if (index < 0 || index >= size)
			return;

		colorIndex[index] = colorCode;
	}

	/**
	 * calculate render order according to direction
	 */
	public void postProcess() {
		outRenderOrder = arrangeRenderOrder(true);
		inRenderOrder = arrangeRenderOrder(false);

		sum = new float[size];
		for (int i = 0; i < size; i++) {
			outgoing[i] = 0;
			incoming[i] = 0;
			for (int j = 0; j < size; j++) {
				outgoing[i] += distri[i][j];
				incoming[i] += distri[j][i];
			}
			sum[i] = incoming[i] + outgoing[i];
		}

		renderOrder = new int[size];

		HeapSort sort = new HeapSort();
		for (int j = 0; j < size; j++) {
			double angle = MBTools.angle(new Vector2d(0, 1), dirs[j]);
			sort.addData(new SortData((float) angle, j));
		}

		List<SortData> sorted = sort.getSortedData();
		for (int j = 0; j < size; j++)
			renderOrder[j] = sorted.get(j).getIndex();
	}

	private int[][] arrangeRenderOrder(boolean out) {
		int[][] renderOrder = new int[size][size - 1];
		for (int i = 0; i < size; i++) {
			HeapSort sort = new HeapSort();
			for (int j = 0; j < size; j++) {
				if (i != j) {
					double angle = 0;
					if (out)
						angle = MBTools.angle(MBTools.rotate(dirs[i],
								-Math.PI / 12),
								MBTools.rotate(
										MBTools.rotate(dirs[j], -Math.PI),
										Math.PI / 12));
					else
						angle = MBTools.angle(MBTools.rotate(dirs[i],
								Math.PI / 12), MBTools.rotate(
								MBTools.rotate(dirs[j], -Math.PI),
								-Math.PI / 12));

					if (j == 0)
						angle = 0;

					sort.addData(new SortData((float) angle, j));
				}
			}
			List<SortData> sortedData = sort.getSortedData();
			for (int j = 0; j < size - 1; j++)
				renderOrder[i][j] = sortedData.get(size - 2 - j).getIndex();
		}
		return renderOrder;
	}

	/**
	 * set the minimum radius to be half average
	 */
	void initMinRadius() {
		float average = 0;
		for (int i = 0; i < size; i++) {
			average += incoming[i] / size;
			average += outgoing[i] / size;
		}

		for (int i = 0; i < size; i++) {
			float ratio = (incoming[i] + outgoing[i]) / average;
			if (ratio < 0.5f) { // make the minimum radius to be 0.5f
				incoming[i] /= ratio;
				outgoing[i] /= ratio;
				for (int j = 0; j < size; j++) {
					incoming[j] += (distri[i][j] / ratio - distri[i][j]);
					outgoing[j] += (distri[j][i] / ratio - distri[j][i]);
					distri[i][j] /= ratio;
					distri[j][i] /= ratio;
				}
			}
		}
	}

	public String toString() {
		String s = "\t";
		for (int i = 0; i < size; i++)
			s += (i + "\t");
		s += "\n";
		for (int i = 0; i < size; i++) {
			s += i + "\t";
			for (int j = 0; j < size; j++)
				s += distri[i][j] + "\t";
			s += "\n";
		}
		return s;
	}

	public float[][] getData() {
		return distri;
	}

	public float[] getOutgoing() {
		return outgoing;
	}

	public float[] getIncoming() {
		return incoming;
	}

	public Vector2d[] getDirections() {
		return dirs;
	}

	public int[] getColorIndex() {
		return colorIndex;
	}

	public int[][] getOutRenderOrder() {
		return outRenderOrder;
	}

	public int[][] getInRenderOrder() {
		return inRenderOrder;
	}

	public int[] getRenderOrder() {
		return renderOrder;
	}

	public float[] getSum() {
		return sum;
	}

	public int size() {
		return size;
	}
}